/** @file cache.c
 *  @brief The cache workload.
 *  
 *  Define the interface that allows the workload generator to stress the cache.
 */
#include <stdlib.h>
#include "cJSON.h"
#include "memory.h"
#include "cache.h"
#include "generator.h"
#include "scheduler.h"

/** 
 * Shorten the reference to the cache buffer struct for convenience.
 */
typedef struct cache_buffer cache_buffer;

/** 
 *   A chunk of memory small enough to fit inside a CPU cache.
 * 
 */ 
struct cache_buffer {
  /** 
   *   The actual chunk of memory to perform I/O on. 
   */
  char *buffer;

  /** A pointer to the block of memory located just outside the buffer.
   *
   * This pointer is used to detect when the generator is outside the allowed chunk of memory.
   */
  char *out_of_bounds;
};

/** The cache buffer used by the l1 cache resource */
cache_buffer l1_cache_buffer;

/** The cache buffer used by the l2 cache resource */
cache_buffer l2_cache_buffer;

/** Sets up a cache buffer from a resource object represented in JSON */
void setup_cache_buffer(cJSON *, cache_buffer *, char *);

/** Holds all the modes for all the cache resources. */
mode_definition_list *cache_modes[HASHSIZE];

/** Set up the L1 Cache's Write Mode. */
mode *setup_l1_cache_write(cJSON *);
/** Set up the L2 Cache's Write Mode. */
mode *setup_l2_cache_write(cJSON *);

/** Write to the L1 cache. */
void * l1_cache_write(void *);
/** Write to the L2 cache. */
void * l2_cache_write(void *);

/** Spawn an L1 Cache Job and Schedule the Next Job */
void create_l1_cache_workload(void *);

/** Spawn an L2 Cache Job and Schedule the Next Job */
void create_l2_cache_workload(void *);


resource_model *setup_l1_cache(cJSON *json) {
  install_mode(cache_modes, "l1_w", setup_l1_cache_write);
  resource_model *model = malloc(sizeof(resource_model));
  setup_rng("l1_cache_arrival", cJSON_GetObjectItem(json,"distribution"));
  setup_cache_buffer(json, &l1_cache_buffer, "random_position_in_l1");
  model->birth = create_l1_cache_workload;
  model->state = NULL;
  model->modes = initialize_modes(cache_modes, json);
  model->cnt_modes = count_modes(json);
  return model;
}

resource_model *setup_l2_cache(cJSON *json) {
  install_mode(cache_modes, "l2_w", setup_l2_cache_write);
  resource_model *model = malloc(sizeof(resource_model));
  setup_rng("l2_cache_arrival", cJSON_GetObjectItem(json,"distribution"));
  setup_cache_buffer(json, &l2_cache_buffer,"random_position_in_l2");
  model->birth = create_l2_cache_workload;
  model->state = NULL;
  model->modes = initialize_modes(cache_modes, json);
  model->cnt_modes = count_modes(json);
  return model;
}

void setup_cache_buffer(cJSON *json, cache_buffer *cache, char *rng_name) {
  cJSON *cache_size = cJSON_GetObjectItem(json,"cache_size");
  unsigned int size = cache_size->valueint - 100; /* Subtract a hundred bytes to make sure we fit inside the cache */
  cache->buffer = malloc(size);
  cache->out_of_bounds = &cache->buffer[size];
  char **uniform = malloc(sizeof(char *));
  asprintf(uniform, "{\"type\":\"uniform\",\"min\":0,\"max\":%lu}", size);
  setup_rng(rng_name,cJSON_Parse(*uniform));
}

void create_l1_cache_workload(void *args) {
  resource_model *model = (resource_model *)args;
  double next_arrival_ms = get_random_number("l1_cache_arrival");
  event *next_birth = malloc(sizeof(event));
  if(next_birth == NULL) {
    perror("malloc");
    exit(1);
  }
  mode *m = select_mode(model);
  next_birth->timestamp = ms_from_now(next_arrival_ms);
  next_birth->job = create_l1_cache_workload;
  next_birth->args = args;
  next_birth->next = NULL;
  pthread_t *thread = malloc(sizeof(pthread_t));
  if(thread == NULL) {
    exit(1);
  }
  pthread_create(thread,NULL,m->function,model->state);
  pthread_detach(*thread);
  schedule_event(next_birth);
}

void create_l2_cache_workload(void *args) {
  resource_model *model = (resource_model *)args;
  double next_arrival_ms = get_random_number("l2_cache_arrival");
  event *next_birth = malloc(sizeof(event));
  if(next_birth == NULL) {
    perror("malloc");
    exit(1);
  }
  mode *m = select_mode(model);
  next_birth->timestamp = ms_from_now(next_arrival_ms);
  next_birth->job = create_l2_cache_workload;
  next_birth->args = args;
  next_birth->next = NULL;
  pthread_t *thread = malloc(sizeof(pthread_t));
  if(thread == NULL) {
    exit(1);
  }
  pthread_create(thread,NULL,m->function,model->state);
  pthread_detach(*thread);
  schedule_event(next_birth);
}

void *l1_cache_write(void *args) {
  cache_buffer *buffer = (cache_buffer *) args;
  unsigned long position = (unsigned long) get_random_number("random_position_in_l1");
  unsigned int bytes_to_write = (unsigned long) get_random_number("l1_cache_w_length");
  memory_write_bytes(l1_cache_buffer.buffer, l1_cache_buffer.out_of_bounds, position, bytes_to_write);
}

void *l2_cache_write(void *args) {
  cache_buffer *buffer = (cache_buffer *) args;
  unsigned long position = (unsigned long) get_random_number("random_position_in_l2");
  unsigned int bytes_to_write = (unsigned long) get_random_number("l2_cache_w_length");
  memory_write_bytes(l2_cache_buffer.buffer, l2_cache_buffer.out_of_bounds, position, bytes_to_write);
}

mode *setup_l1_cache_write(cJSON *json) {
  mode *m = malloc(sizeof(mode));
  m->function = l1_cache_write;
  cJSON *json_probability = cJSON_GetObjectItem(json,"p");
  setup_rng("l1_cache_w_length", cJSON_GetObjectItem(json,"length"));
  m->key = "l1_w";
  m->function = l1_cache_write;
  m->probability = json_probability->valuedouble;
  return m;
}

mode *setup_l2_cache_write(cJSON *json) {
  mode *m = malloc(sizeof(mode));
  m->function = l2_cache_write;
  cJSON *json_probability = cJSON_GetObjectItem(json,"p");
  setup_rng("l2_cache_w_length", cJSON_GetObjectItem(json,"length"));
  m->key = "l2_w";
  m->function = l2_cache_write;
  m->probability = json_probability->valuedouble;
  return m;
}
